/**************************************

usage:
$p = `polySphere -r 1 -sx 8 -sy 8 -ax 0 1 0 -cuv 2 -ch 1`;
$loc = `createNode "GrassLocator"`;
connectAttr ($p[0] +".worldMesh") ($loc+".surface");

*************************************/



//This example was inspired by Rob from robthebloke.org.  

#include <maya/MFnMesh.h>
#include <maya/MMeshIntersector.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MDagPath.h>

#include <vector>
#include <map>
#include <valarray>
using namespace std;

#include "GrassLocator.h"

/// the unique ID for our locator node
//  I don't have a list of ID's from alias - you should 
//  change this as there is a chance it'll conflict
const MTypeId GrassLocator::typeId( 0x80001 );

/// the typename of our node
const MString GrassLocator::typeName( "GrassLocator" );
typedef vector< vector< uvId > > FaceUVMap;

/// attributes for the locator
MObject GrassLocator::densityAttr;
MObject GrassLocator::lengthAttr;
MObject GrassLocator::droopAttr;
MObject GrassLocator::surfaceAttr;
MObject GrassLocator::sentinelAttr;


static void getRandomUV(float &u, float &v){
	u = ((float) rand())/ ((float) RAND_MAX);
	v = ((float) rand())/ ((float) RAND_MAX);
}

MStatus GrassLocator::compute(const MPlug &plug, MDataBlock &dataBlock){
	MStatus status;
	//we only care about the sentinelAttr, ignore stuff like localScale
	if(plug == sentinelAttr){		

		MDataHandle outSent = dataBlock.outputValue(sentinelAttr);
		outSent.set(dSent +1 % 10); //increment, this tells the draw routine that the display list needs to be regenerated
		dataBlock.setClean(sentinelAttr);

		MObject meshObj = dataBlock.inputValue(surfaceAttr).asMesh(); //get input mesh
		if(meshObj == MObject::kNullObj){
			return MS::kFailure;
		}

		MDagPath meshDag;
		MDagPath::getAPathTo(meshObj, meshDag);
		MFnMesh fnMesh(meshObj);
		MItMeshPolygon faceIter(meshObj);
		faceIter.reset();

		//grass attributes, you can add plenty more like width, etc
		const float density = dataBlock.inputValue(densityAttr).asFloat();
		const float length = dataBlock.inputValue(lengthAttr).asFloat();
		const float droop = dataBlock.inputValue(droopAttr).asFloat();

		//this would need to generated by the same process as your render
		//DSO or whatever you're doing
		const int randSeed = 7; //this could be an input attribute as well
		srand(randSeed);
	
		const float nBladesPerFaceArea = (200.0f * density);
	
		float u,v;
		MPoint pt;
		MVector norm, tan;
		const int nPolys = fnMesh.numPolygons();


		MMeshIntersector inter;
		inter.create(meshObj);

		lawn.clear();
		MPointOnMesh meshPt;
		//for each face		
		//    for 0 to number of blades
		//			generate a random UV , and if it's on the face
		//			get closest point and normal to that UV
		//	
		//Maya doesn't just let you get a point at a uv - you're stuck with a face iterator (annoying)
		// to do it the right way you're going need an acceleration structure (kd tree).
		// Rather than do that, we'll just grow a few blades on each 
		// face.  The point of this example is 
		// drawing and maya dependencies not kd-trees

		for(; !faceIter.isDone(); faceIter.next()){		
			//const size_t nVerts = face2UVMap.size(); //how many face verts
			int cnt = 0;
			double area;
			faceIter.getArea(area);
			const int nB = nBladesPerFaceArea * ((float) area);
			while(cnt < nB){
						
				//generateUVMixture(u,v, face2UVMap[f], uArray, vArray); //fake random barycentric coords
				getRandomUV(u, v);
				float uv[2] = {u, v};
			
				//get point on surface
				//fnMesh.getPointAtUV(f, pt, uv);			 		
				status = faceIter.getPointAtUV(pt, uv, MSpace::kWorld);
				if(!status){
					//cout << "Face Iter FAIL"<<endl;
					continue;
				}else{
					cnt++;
				}

				inter.getClosestPoint(pt, meshPt); //this gets a structure with both point and normal
													// the MMeshIntersector uses an octree (unlike getClosestPointAndNormal)
				//generate blade
				GrassBlade blade;
				MVector bp = MVector(meshPt.getPoint());
				blade.push_back(bp);
				MVector n = meshPt.getNormal();
			
				//add points that define the blade
				for(int i = 1; i < 5; i++){
					MVector nP = bp + n * length * ((float) i) / 5.0f ; //point in direction of normal
					nP.y -= (((float) i) / 5.0f) * (((float) i) / 5.0f) * droop * 0.4f; //throw in a little curl
					blade.push_back( nP);
				}
				//if you want blades to have an oriented width, you're going to need to get tangent info as well								

				//add to lawn
				lawn.push_back(blade);
			}
		}
		

	}
	return MS::kSuccess;
}

//called when OpenGL decides it's time to draw
void GrassLocator::draw( M3dView& view,
						  const MDagPath& DGpath,
						  M3dView::DisplayStyle style,
						  M3dView::DisplayStatus status ){

	//get vector of grass blades to draw
	bool updateDList = GetPlugData(); //updateDList flag indicates if we need to update the display list

	//a maya specific GL call
	view.beginGL();

	// store the current color and openGL settings
	glPushAttrib( GL_CURRENT_BIT );
	if(updateDList){
		if(dList == 0){
			dList = glGenLists(1);
		}
		glNewList(dList, GL_COMPILE);
		drawGrass();
		glEndList();
	}
		
	glCallList(dList);
	glPopAttrib();

	view.endGL(); //maya open GL stuff
}

//draw loop for grass
//just draw line strips
//if you had a width to the grass, you could draw triangle strips
void GrassLocator::drawGrass(){

	for(Lawn::const_iterator lIt = lawn.begin(); lIt != lawn.end(); ++lIt){
		glBegin(GL_LINE_STRIP);
		for(GrassBlade::const_iterator bIt = lIt->begin(); bIt != lIt->end(); ++bIt){
			glVertex3f(bIt->x, bIt->y, bIt->z);
		}
		glEnd();
	}
}

//THIS IS THE WHOLE POINT OF THIS EXAMPLE
//If we query the sentinel plug, and the input connections 
//to this node are dirty, then the node will compute()
//that causes our grass to update
bool GrassLocator::GetPlugData() {
	MStatus stat;

	MObject thisNode = thisMObject();

	int sent =0;

	// get sentinel plug, causes update
	MPlug SentinelPlug(thisNode, GrassLocator::sentinelAttr );
	SentinelPlug.getValue( sent );
	if(sent != dSent){
		dSent = sent;
		return true;
	}else{
		return false;
	}

}

// returns the bounding box of the locator node
// Unfortunately using GetPlugData will not work
// as the flag to regenerate display list update is lost
MBoundingBox GrassLocator::boundingBox() const
{
	MObject thisNode = thisMObject();

	int sent =0;
	// get sentinel plug, causes update
	MPlug SentinelPlug(thisNode, GrassLocator::sentinelAttr );
	SentinelPlug.getValue( sent );

	// expand the bounding box to fit all axes of the locator node
	MBoundingBox bbox;

	for(Lawn::const_iterator lIt = lawn.begin(); lIt != lawn.end(); ++lIt){
		for(GrassBlade::const_iterator bIt = lIt->begin(); bIt != lIt->end(); ++bIt){
			bbox.expand(*bIt);
		}
	}

	return bbox;
}

bool GrassLocator::isBounded() const{
	return true;
}

// this function is called by maya to return a new instance of our locator node
// the new node
//
void* GrassLocator::creator()
{
	return new GrassLocator();
}


/// this function creates a description of our node
/// The status code
///
MStatus GrassLocator::initialize()
{	
	MStatus status;

	//define surface attribute
	MFnTypedAttribute tAttr;
	surfaceAttr = tAttr.create("surface", "srf", MFnData::kMesh, MObject::kNullObj, &status);
	tAttr.setWritable(true);
	if(!status){
		status.perror("create mesh FAIL");
		return status;
	}
	addAttribute(surfaceAttr);

	//define density attribute
	MFnNumericAttribute nAttr;
	densityAttr = nAttr.create("density", "den", MFnNumericData::kFloat, 0.5);
	nAttr.setKeyable(true);
	addAttribute(densityAttr);

	//define length attribute
	lengthAttr = nAttr.create("length", "len", MFnNumericData::kFloat, 0.1);
	nAttr.setMin(0.0000001);
	nAttr.setKeyable(true);
	addAttribute(lengthAttr);

	//define droop attribute
	droopAttr = nAttr.create("droop", "dp", MFnNumericData::kFloat, 0.1);
	nAttr.setMin(0.0);
	nAttr.setKeyable(true);
	addAttribute(droopAttr);
	
	//sentinel attr
	sentinelAttr = nAttr.create("sentinel", "sent", MFnNumericData::kInt, 0);
	nAttr.setHidden(true);
	addAttribute(sentinelAttr);

	//this tells maya what inputs affect what outputs
	attributeAffects(surfaceAttr, sentinelAttr);
	attributeAffects(densityAttr, sentinelAttr);
	attributeAffects(lengthAttr, sentinelAttr);
	attributeAffects(droopAttr, sentinelAttr);
	return MS::kSuccess;
}

